/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.search;

import org.apache.lucene.util.OpenBitSet;
import org.apache.lucene.util.OpenBitSetIterator;
import org.apache.lucene.search.DocIdSetIterator;

/**
 * <code>BitDocSet</code> represents an unordered set of Lucene Document Ids
 * using a BitSet. A set bit represents inclusion in the set for that document.
 * 
 * @version $Id: BitDocSet.java 1065312 2011-01-30 16:08:25Z rmuir $
 * @since solr 0.9
 */
public class BitDocSet extends DocSetBase {
	final OpenBitSet bits;
	int size; // number of docs in the set (cached for perf)

	public BitDocSet() {
		bits = new OpenBitSet();
	}

	/**
	 * Construct a BitDocSet. The capacity of the OpenBitSet should be at least
	 * maxDoc()
	 */
	public BitDocSet(OpenBitSet bits) {
		this.bits = bits;
		size = -1;
	}

	/**
	 * Construct a BitDocSet, and provides the number of set bits. The capacity
	 * of the OpenBitSet should be at least maxDoc()
	 */
	public BitDocSet(OpenBitSet bits, int size) {
		this.bits = bits;
		this.size = size;
	}

	/***
	 * DocIterator using nextSetBit() public DocIterator iterator() { return new
	 * DocIterator() { int pos=bits.nextSetBit(0); public boolean hasNext() {
	 * return pos>=0; }
	 * 
	 * public Integer next() { return nextDoc(); }
	 * 
	 * public void remove() { bits.clear(pos); }
	 * 
	 * public int nextDoc() { int old=pos; pos=bits.nextSetBit(old+1); return
	 * old; }
	 * 
	 * public float score() { return 0.0f; } }; }
	 ***/

	public DocIterator iterator() {
		return new DocIterator() {
			private final OpenBitSetIterator iter = new OpenBitSetIterator(bits);
			private int pos = iter.nextDoc();

			public boolean hasNext() {
				return pos != DocIdSetIterator.NO_MORE_DOCS;
			}

			public Integer next() {
				return nextDoc();
			}

			public void remove() {
				bits.clear(pos);
			}

			public int nextDoc() {
				int old = pos;
				pos = iter.nextDoc();
				return old;
			}

			public float score() {
				return 0.0f;
			}
		};
	}

	/**
	 * 
	 * @return the <b>internal</b> OpenBitSet that should <b>not</b> be
	 *         modified.
	 */
	@Override
	public OpenBitSet getBits() {
		return bits;
	}

	@Override
	public void add(int doc) {
		bits.set(doc);
		size = -1; // invalidate size
	}

	@Override
	public void addUnique(int doc) {
		bits.set(doc);
		size = -1; // invalidate size
	}

	public int size() {
		if (size != -1)
			return size;
		return size = (int) bits.cardinality();
	}

	/**
	 * The number of set bits - size - is cached. If the bitset is changed
	 * externally, this method should be used to invalidate the previously
	 * cached size.
	 */
	public void invalidateSize() {
		size = -1;
	}

	public boolean exists(int doc) {
		return bits.get(doc);
	}

	@Override
	public int intersectionSize(DocSet other) {
		if (other instanceof BitDocSet) {
			return (int) OpenBitSet.intersectionCount(this.bits,
					((BitDocSet) other).bits);
		} else {
			// they had better not call us back!
			return other.intersectionSize(this);
		}
	}

	@Override
	public int unionSize(DocSet other) {
		if (other instanceof BitDocSet) {
			// if we don't know our current size, this is faster than
			// size + other.size - intersection_size
			return (int) OpenBitSet.unionCount(this.bits,
					((BitDocSet) other).bits);
		} else {
			// they had better not call us back!
			return other.unionSize(this);
		}
	}

	@Override
	public int andNotSize(DocSet other) {
		if (other instanceof BitDocSet) {
			// if we don't know our current size, this is faster than
			// size - intersection_size
			return (int) OpenBitSet.andNotCount(this.bits,
					((BitDocSet) other).bits);
		} else {
			return super.andNotSize(other);
		}
	}

	@Override
	public DocSet andNot(DocSet other) {
		OpenBitSet newbits = (OpenBitSet) (bits.clone());
		if (other instanceof BitDocSet) {
			newbits.andNot(((BitDocSet) other).bits);
		} else {
			DocIterator iter = other.iterator();
			while (iter.hasNext())
				newbits.clear(iter.nextDoc());
		}
		return new BitDocSet(newbits);
	}

	@Override
	public DocSet union(DocSet other) {
		OpenBitSet newbits = (OpenBitSet) (bits.clone());
		if (other instanceof BitDocSet) {
			newbits.union(((BitDocSet) other).bits);
		} else {
			DocIterator iter = other.iterator();
			while (iter.hasNext())
				newbits.set(iter.nextDoc());
		}
		return new BitDocSet(newbits);
	}

	public long memSize() {
		return (bits.getBits().length << 3) + 16;
	}
}
